//go:build linux && arm

package sys

import (
	"encoding/binary"
	"fmt"
	"math/rand/v2"
	"net"
	"os"
	"os/exec"
	"strconv"
	"sync/atomic"
	"syscall"
)

func Daemonize(childArgs []string) (*exec.Cmd, error) {
	cmd := exec.Command(os.Args[0], childArgs...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Stdin = os.Stdin
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Setsid: true,
	}
	err := cmd.Start()
	return cmd, err
}

type FakeSocket struct {
	fd      int
	v6      bool
	fakeIp  net.IP
	dstIp   net.IP
	dstPort int
	seq     atomic.Uint64
}

func (f *FakeSocket) Open(v6 bool, fakeIp, dstIp, dstPort string) bool {
	var err error
	f.v6 = v6
	f.dstPort, err = strconv.Atoi(dstPort)
	if err != nil {
		return false
	}
	if f.v6 {
		if !f.parseV6(fakeIp, dstIp) {
			return false
		}
		f.fd, err = syscall.Socket(syscall.AF_INET6, syscall.SOCK_DGRAM, syscall.IPPROTO_RAW)
	} else {
		if !f.parseV4(fakeIp, dstIp) {
			return false
		}
		f.fd, err = syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_RAW)
	}
	if err != nil {
		return false
	}
	f.seq.Store(rand.Uint64N(65536))
	return true
}

func (f *FakeSocket) Close() {
	_ = syscall.Close(f.fd)
}

func (f *FakeSocket) Send(payload []byte) (int, error) {
	if f.v6 {
		return f.writeV4(payload)
	}
	return f.writeV6(payload)
}

func (f *FakeSocket) parseV4(fakeIp, dstIp string) bool {
	f.fakeIp = net.ParseIP(fakeIp).To4()
	f.dstIp = net.ParseIP(dstIp).To4()
	if f.fakeIp == nil || f.dstIp == nil {
		return false
	}
	return true
}
func (f *FakeSocket) parseV6(fakeIp, dstIp string) bool {
	f.fakeIp = net.ParseIP(fakeIp).To16()
	f.dstIp = net.ParseIP(dstIp).To16()
	if f.fakeIp == nil || f.dstIp == nil {
		return false
	}
	return true
}

func (f *FakeSocket) writeV4(payload []byte) (int, error) {
	if f.fd < 1 {
		return -1, fmt.Errorf("sokcet v4 not opened")
	}
	udpLength := uint16(8 + len(payload))
	totalLength := 20 + udpLength
	seqId := uint16(f.seq.Add(1) % 65536)
	ipHeader := []byte{
		0x45, 0x00,
		byte(totalLength >> 8), byte(totalLength),
		byte(seqId >> 8), byte(seqId),
		0x40, 0x00,
		0x40,
		syscall.IPPROTO_UDP,
		0x00, 0x00,
		0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00,
	}
	copy(ipHeader[12:16], f.fakeIp)
	copy(ipHeader[16:20], f.dstIp)
	ipChecksum := calculateChecksum(ipHeader)
	binary.BigEndian.PutUint16(ipHeader[10:12], ipChecksum)
	srcPort := uint16(50000 + rand.IntN(10000))
	udpHeader := []byte{
		byte(srcPort >> 8), byte(srcPort),
		byte(f.dstPort >> 8), byte(f.dstPort),
		byte(udpLength >> 8), byte(udpLength),
		0x00, 0x00,
	}
	pseudoHeader := make([]byte, 12)
	copy(pseudoHeader[0:4], f.fakeIp)
	copy(pseudoHeader[4:8], f.dstIp)
	pseudoHeader[9] = syscall.IPPROTO_UDP
	binary.BigEndian.PutUint16(pseudoHeader[10:12], udpLength)
	checkData := append(pseudoHeader, udpHeader...)
	checkData = append(checkData, payload...)
	udpChecksum := calculateChecksum(checkData)
	binary.BigEndian.PutUint16(udpHeader[6:8], udpChecksum)
	packet := make([]byte, 0, totalLength)
	packet = append(packet, ipHeader...)
	packet = append(packet, udpHeader...)
	packet = append(packet, payload...)
	destAddr := syscall.SockaddrInet4{
		Port: 0,
	}
	copy(destAddr.Addr[:], f.dstIp)
	err := syscall.Sendto(f.fd, packet, 0, &destAddr)
	if err != nil {
		return -1, err
	}
	return len(payload), nil
}

func (f *FakeSocket) writeV6(payload []byte) (int, error) {
	if f.fd < 1 {
		return -1, fmt.Errorf("sokcet v6 not opened")
	}
	udpLength := uint16(8 + len(payload))
	totalLength := 20 + udpLength
	ipHeader := make([]byte, 40)
	ipHeader[0] = 0x60
	binary.BigEndian.PutUint16(ipHeader[1:3], 0)
	binary.BigEndian.PutUint16(ipHeader[4:6], udpLength)
	ipHeader[6] = syscall.IPPROTO_UDP
	ipHeader[7] = 64
	copy(ipHeader[8:24], f.fakeIp)
	copy(ipHeader[24:40], f.dstIp)
	srcPort := uint16(50000 + rand.IntN(10000))
	udpHeader := []byte{
		byte(srcPort >> 8), byte(srcPort),
		byte(f.dstPort >> 8), byte(f.dstPort),
		byte(udpLength >> 8), byte(udpLength),
		0x00, 0x00,
	}
	pseudoHeader := make([]byte, 40)
	copy(pseudoHeader[0:16], f.fakeIp)
	copy(pseudoHeader[16:32], f.dstIp)
	binary.BigEndian.PutUint32(pseudoHeader[32:36], uint32(len(udpHeader)+len(payload)))
	pseudoHeader[39] = syscall.IPPROTO_UDP
	checkData := append(pseudoHeader, udpHeader...)
	checkData = append(checkData, payload...)
	udpChecksum := calculateChecksum(checkData)
	binary.BigEndian.PutUint16(udpHeader[6:8], udpChecksum)
	packet := make([]byte, 0, totalLength)
	packet = append(packet, ipHeader...)
	packet = append(packet, udpHeader...)
	packet = append(packet, payload...)
	destAddr := syscall.SockaddrInet6{
		Port: 0,
	}
	copy(destAddr.Addr[:], f.dstIp)
	err := syscall.Sendto(f.fd, packet, 0, &destAddr)
	if err != nil {
		return -1, err
	}
	return len(payload), nil
}

func calculateChecksum(data []byte) uint16 {
	var sum uint32
	for i := 0; i < len(data); i += 2 {
		var word uint16
		if i+1 < len(data) {
			word = binary.BigEndian.Uint16(data[i : i+2])
		} else {
			word = uint16(data[i]) << 8
		}
		sum += uint32(word)
	}
	for sum > 0xffff {
		sum = (sum >> 16) + (sum & 0xffff)
	}
	return uint16(^sum)
}
